
#include "session.h"

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <asm/shdev.h>
#endif
#include <asm/delay.h>

#include <uapi/stdlib.h>
#include <uapi/string.h>

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include "ff/ff.h"
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#else
#include "ff.h"

#if !defined(CONFIG_HAVE_DATBUFS) || defined(CONFIG_NO_DATBUFS)
#error configuration CONFIG_HAVE_DATBUFS must be defined, configuration CONFIG_NO_DATBUFS must be deleted !
#endif
#endif

#if CONFIG_MEDIA_SAVE_DECODE_FILES
//pcm play cmd: ffplay -ac 2 -ar 44100 -f f32le ffadecoder.pcm
static FILE *outfile;
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
// XA_MP3
///////////////////////////////////////////////////////////////////////////////////////////////

#define XA_USER_STATIC static

#include "xa_type_def.h"
#include "xa_memory_standards.h"
#include "xa_error_handler.h"
#include "mp3_dec/xa_mp3_dec_api.h"

/* error handler */
#define xa_error_printf debug
#include "../test/src/xa_dec_error_handler.c"
#include "../test/src/xa_mp3_dec_error_handler.c"

/* xa_dec lib */
#define CODEC_FUNC xa_mp3_dec

// string limit
#define MAX_MEM_ALLOCS 100
#define XA_SCREEN_WIDTH 80
#define XA_MAX_CMD_LINE_LENGTH 300

/* xa_dec api */
typedef struct
{
	pVOID ptr;
	UWORD32 type;
	UWORD32 size;
	UWORD32 aligned;
}xa_dec_mem_table_t;

typedef struct
{
	xa_codec_handle_t p_xa_process_api_obj;
	xa_codec_func_t *p_xa_process_api;
	xa_error_info_struct *p_proc_err_info;

	UWORD32 proc_mem_tabs_size, proc_mem_tabs_count, audio_valid_size;

	pVOID proc_mem_tabs_ptr;
	xa_dec_mem_table_t proc_mem_tabs[MAX_MEM_ALLOCS];

	int junkdone, eof;
	sysbuf_t input, output, *audiobuf;

	WORD32 outnchans;
	audio_info_t streamcfg;
	audio_info_t audiocfg;

#ifdef MEDIA_AV_SYNC
	media_pkt_sync_t sync_pkt[2];
#endif
}xa_dec_unit_t;

#define API_CALL(cmd, idx, pvalue, errstr) \
		do { err_code = (*punit->p_xa_process_api)(punit->p_xa_process_api_obj, (cmd), (idx), (pvalue)); \
		_XA_HANDLE_ERROR(punit->p_proc_err_info, (errstr), err_code); }while(0)

#define SET_CONFIG(idx, pvalue, errstr) \
		API_CALL(XA_API_CMD_SET_CONFIG_PARAM,idx,pvalue,errstr)

#define GET_CONFIG(idx, pvalue, errstr) \
		API_CALL(XA_API_CMD_GET_CONFIG_PARAM,idx,pvalue,errstr)

#define API_CALLR(cmd, idx, pvalue, errstr, _r) \
	do { err_code = (*punit->p_xa_process_api)(punit->p_xa_process_api_obj, (cmd), (idx), (pvalue)); \
	_XA_HANDLE_ERROR_RETURN(punit->p_proc_err_info, (errstr), err_code, _r); }while(0)

#define SET_CONFIGR(idx, pvalue, errstr, _r) \
		API_CALLR(XA_API_CMD_SET_CONFIG_PARAM,idx,pvalue,errstr,_r)

#define GET_CONFIGR(idx, pvalue, errstr, _r) \
		API_CALLR(XA_API_CMD_GET_CONFIG_PARAM,idx,pvalue,errstr,_r)

////////////////////////////////////////////////////////////////

static XA_ERRORCODE XaDec_LoadLibrary(xa_dec_unit_t *punit)
{
	XA_ERRORCODE err_code = XA_NO_ERROR;

	WORD8 pb_process_name[XA_SCREEN_WIDTH] = "";
	WORD8 pb_lib_version[XA_SCREEN_WIDTH] = "";
	UWORD32 ui_api_size = 0;

	/* Call decoder error handler init */
	xa_mp3_dec_error_handler_init();

	punit->p_xa_process_api = &CODEC_FUNC;
	punit->p_proc_err_info = &xa_mp3_dec_error_info;
	punit->p_xa_process_api_obj = NULL;

	/* ******************************************************************/
	/* Get the library name, library version and API version            */
	/* ******************************************************************/

	/* Get the library name string */
	API_CALL(XA_API_CMD_GET_LIB_ID_STRINGS, XA_CMD_TYPE_LIB_NAME, pb_process_name, "Lib-name Error");

	/* Get the library version string */
	API_CALL(XA_API_CMD_GET_LIB_ID_STRINGS, XA_CMD_TYPE_LIB_VERSION, pb_lib_version, "Lib-version Error");

	/* Display the Tensilica identification message */
	debug("\t%s version %s\n", pb_process_name, pb_lib_version);

	/* ******************************************************************/
	/* Initialize API structure and set config params to default        */
	/* ******************************************************************/

	/* Get the API size */
	API_CALL(XA_API_CMD_GET_API_SIZE, 0, &ui_api_size, "Api Size Error");

	/* Set API object with the memory allocated */
	punit->p_xa_process_api_obj = (xa_codec_handle_t)memalign(4, ui_api_size);
	if (punit->p_xa_process_api_obj == NULL) {
		punit->p_xa_process_api = NULL;
		punit->p_proc_err_info = NULL;
		return XA_API_FATAL_MEM_ALLOC;
	}

	/* ******************************************************************/
	/* Set the config params to default values                          */
	/* ******************************************************************/
	API_CALL(XA_API_CMD_INIT, XA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS, NULL, "Pre Configuration Init Error");

	return XA_NO_ERROR;
}

static void XaDec_FreeLibrary(xa_dec_unit_t *punit)
{
	if (punit == NULL)
		return;

	if (punit->p_xa_process_api_obj != NULL)
		free(punit->p_xa_process_api_obj);

	memset(punit, 0, sizeof(xa_dec_unit_t));

}

#define SAMPLERATE_COUNT 12
const static WORD32 _srate[SAMPLERATE_COUNT] =
{ 8000, 11025, 12000, 16000, 22050, 24000,32000, 44100, 48000, 64000, 88200,96000, };
static WORD32 XaDec_GetNearlySamplingrate(WORD32 rate)
{
	int i;

	if (rate < _srate[0])
		rate = _srate[0];
	else if (rate > _srate[SAMPLERATE_COUNT - 1])
		rate = _srate[SAMPLERATE_COUNT - 1];

	for (i = 1; i < SAMPLERATE_COUNT; i++) {
		if (_srate[i - 1] <= rate && rate <= _srate[i]) {
			if (_srate[i] - rate < rate - _srate[i - 1])
				return _srate[i];
			else
				return _srate[i - 1];
		}
	}

	// default & never here
	return 44100;

}

static XA_ERRORCODE XaDec_SetConfigure(xa_dec_unit_t *punit)
{
	XA_ERRORCODE err_code = XA_NO_ERROR;
	WORD32 ui_value;

	if (punit == NULL)
		return XA_FATAL_ERROR;

	/* PCM word size 16 or 24. Default value is 16 */
	ui_value = (WORD32)punit->streamcfg.samplingsize;
	SET_CONFIG(XA_MP3DEC_CONFIG_PARAM_PCM_WDSZ, \
		(void *)&ui_value, "PCM sample size Configure Error");

#if 0
	/* Flag to enable header CRC check. Default value is 0 */
	ui_value = 0;
	SET_CONFIG(XA_MP3DEC_CONFIG_PARAM_CRC_CHECK, \
		(void *)&ui_value, "Set header CRC check Error");

	/* Flag to enable/disable Multi-Channel decoding. (available only on mp3mch_dec) Default value is 1 */
	ui_value = 1;
	SET_CONFIG(XA_MP3DEC_CONFIG_PARAM_MCH_ENABLE, \
		(void *)&ui_value, "SET Multi-Channel decoding Error");
#endif

	return err_code;
}

static XA_ERRORCODE XaDec_GetParams(xa_dec_unit_t *punit)
{
	XA_ERRORCODE err_code = XA_NO_ERROR;
	WORD32 ui_value;

	if (punit == NULL)
		return XA_FATAL_ERROR;

	/* Maximum number of channels supported by decoder (limits: library dependent) */
	GET_CONFIG(XA_MP3DEC_CONFIG_PARAM_NUM_CHANNELS, \
		(void *)&ui_value, "Max Number channel Configure Error");
	punit->outnchans = ui_value;

	/* PCM sample size (16 or 24 - default) */
	GET_CONFIG(XA_MP3DEC_CONFIG_PARAM_PCM_WDSZ, \
		(void *)&ui_value, "PCM sample size Configure Error");
	punit->audiocfg.samplingsize = ui_value;

	/* Sampling rate of output */
	GET_CONFIG(XA_MP3DEC_CONFIG_PARAM_SAMP_FREQ, \
		(void *)&ui_value, "Sample Rate Configure Error");
	punit->audiocfg.samplingrate = ui_value;

	return err_code;
}

static XA_ERRORCODE XaDec_AllocMemory(xa_dec_unit_t *punit)
{
	UWORD32 i;
	XA_ERRORCODE err_code = XA_NO_ERROR;

	/* ******************************************************************/
	/* Initialize Memory info tables                                    */
	/* ******************************************************************/

	/* Get memory info tables size and Set pointer for process memory tables*/
	API_CALL(XA_API_CMD_GET_MEMTABS_SIZE, 0, &punit->proc_mem_tabs_size, "");

	punit->proc_mem_tabs_ptr = memalign(4, punit->proc_mem_tabs_size);
	if (punit->proc_mem_tabs_ptr == NULL) {
		return XA_API_FATAL_MEM_ALLOC;
	}

	API_CALL(XA_API_CMD_SET_MEMTABS_PTR, 0, punit->proc_mem_tabs_ptr, "Set mem-tab-ptr error");

	/* initialize the API, post configure, fill memory tables */
	API_CALL(XA_API_CMD_INIT, XA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS, NULL, "post-config error");

	/*******************************************************************/
	/* Allocate Memory with info from library                          */
	/*******************************************************************/

	/* Get number of memory tables required */
	API_CALL(XA_API_CMD_GET_N_MEMTABS, 0, &punit->proc_mem_tabs_count, "Get-n-memtabs error");

	memset(punit->proc_mem_tabs, 0, sizeof(punit->proc_mem_tabs));

	for (i = 0; i < punit->proc_mem_tabs_count; i++)
	{
		UWORD32 ui_size = 0, ui_alignment = 1, ui_type = -1;
		pVOID pv_alloc_ptr;

		/* Get memory size */
		API_CALL(XA_API_CMD_GET_MEM_INFO_SIZE, i, &ui_size, "Error in get-mem-info-size");

		/* Get memory alignment */
		API_CALL(XA_API_CMD_GET_MEM_INFO_ALIGNMENT, i, &ui_alignment, "Error in get-mem-info-alignment");

		/* Get memory type */
		API_CALL(XA_API_CMD_GET_MEM_INFO_TYPE, i, &ui_type, "Error in get-mem-info-type");

		/* read-write memory, for usable output, intra frame */
		pv_alloc_ptr = memalign(ui_alignment, ui_size);
		if (pv_alloc_ptr == NULL)
			return XA_API_FATAL_MEM_ALLOC;

		/* Set the buffer pointer */
		API_CALL(XA_API_CMD_SET_MEM_PTR, i, pv_alloc_ptr, "Error in set mem-ptr");

		punit->proc_mem_tabs[i].ptr = pv_alloc_ptr;
		punit->proc_mem_tabs[i].type = ui_type;
		punit->proc_mem_tabs[i].size = ui_size;
		punit->proc_mem_tabs[i].aligned = ui_alignment;

		if (i == XA_MEMTYPE_INPUT) {
			punit->input.haddr = (phys_addr_t)pv_alloc_ptr;
			punit->input.maxsize = ui_size;
			punit->input.offset = 0;
			punit->input.size = 0;
		}
		else if (i == XA_MEMTYPE_OUTPUT) {
			punit->output.haddr = (phys_addr_t)pv_alloc_ptr;
			punit->output.maxsize = ui_size;
			punit->output.offset = 0;
			punit->output.size = 0;
		}

	}

	return XA_NO_ERROR;
}

static XA_ERRORCODE XaDec_FreeMemory(xa_dec_unit_t *punit)
{
	UWORD32 i;
	XA_ERRORCODE err_code = XA_NO_ERROR;

	if (punit == NULL)
		return;

	for (i = 0; i < punit->proc_mem_tabs_count; i++)
	{
		if (punit->proc_mem_tabs[i].ptr != NULL) {
			free(punit->proc_mem_tabs[i].ptr);
		}
	}

	if (punit->proc_mem_tabs_ptr != NULL)
		free(punit->proc_mem_tabs_ptr);

	memset(punit->proc_mem_tabs, 0, sizeof(punit->proc_mem_tabs));

	punit->proc_mem_tabs_ptr = NULL;

	if (punit->audiobuf) {
		sysbuf_free(punit->audiobuf);
		punit->audiobuf = NULL;
	}

	return XA_NO_ERROR;
}

static XA_ERRORCODE XaDec_Prepare(xa_dec_unit_t *punit)
{
	XA_ERRORCODE err_code = XA_NO_ERROR;
	WORD32 ui_value;

	if (punit == NULL)
		return XA_FATAL_ERROR;

	// do configurations
	err_code = XaDec_SetConfigure(punit);
	if (err_code & XA_FATAL_ERROR)
		return err_code;

	// alloc memory
	err_code = XaDec_AllocMemory(punit);
	if (err_code != XA_NO_ERROR) {
		XaDec_FreeMemory(punit);
		return err_code;
	}

	return XA_NO_ERROR;
}

static void XaDec_Release(xa_dec_unit_t *punit)
{
	if (punit == NULL)
		return;

	// free all memory
	XaDec_FreeMemory(punit);

	return;
}

static int xa_shift_input_buffer(signed char *buf, int buf_size, int bytes_consumed)
{
	int i;
	ae_p16s *dst, *src;

	if (bytes_consumed <= 0)
		return buf_size;

	/* Optimize 2-byte aligned data movement. */
	if ((((uint32_t)buf | buf_size | bytes_consumed) & 1) == 0) {
		/* Optimize 4-byte aligned data movement. */
		if ((((uint32_t)buf | buf_size | bytes_consumed) & 2) == 0) {
			ae_p16x2s *dst = (ae_p16x2s *)buf;
			ae_p16x2s *src = (ae_p16x2s *)&buf[bytes_consumed];
			for (i = 0; i < (buf_size - bytes_consumed) >> 2; i++) {
				dst[i] = src[i];
			}
			return (buf_size - bytes_consumed);
		}

		dst = (ae_p16s *)buf;
		src = (ae_p16s *)&buf[bytes_consumed];
		for (i = 0; i < (buf_size - bytes_consumed) >> 1; i++) {
			dst[i] = src[i];
		}

		return (buf_size - bytes_consumed);
	}

	/* Default, non-aligned data movement. */
	for (i = 0; i < buf_size - bytes_consumed; i++) {
		buf[i] = buf[i + bytes_consumed];
	}

	return (buf_size - bytes_consumed);
}

static inline long output_wordsize(long sample_bits)
{
	/* Round up to the next 2-byte size: 16 -> 2; 24 -> 4. */
	return 2 * ((sample_bits + 15) / 16);
}

static inline void
pack_32_to_24_bits(UWORD8 *pb_out_ptr, UWORD32 input)
{
#if __XTENSA_EL__
	pb_out_ptr[0] = ((WORD32)input >> 8) & 0xff;
	pb_out_ptr[1] = ((WORD32)input >> 16) & 0xff;
	pb_out_ptr[2] = ((WORD32)input >> 24) & 0xff;
#else  /* __XTENSA_EL__ */
	pb_out_ptr[2] = ((WORD32)input >> 8) & 0xff;
	pb_out_ptr[1] = ((WORD32)input >> 16) & 0xff;
	pb_out_ptr[0] = ((WORD32)input >> 24) & 0xff;
#endif  /* __XTENSA_EL__ */
}

static long buffer_32_to_24_bits(UWORD8 *pb_out_buf,
	UWORD32 *pb_in_buf, long i_out_bytes
)
{
	long i, ui_nsamples = (i_out_bytes / output_wordsize(24));
	for (i = 0; i < ui_nsamples; i++, pb_out_buf += 3) {
		pack_32_to_24_bits(pb_out_buf, *pb_in_buf++);
	}
	return (ui_nsamples * 24) / 8;
}

#ifdef MEDIA_AV_SYNC
static int update_pts(xa_dec_unit_t *punit, psysbuf_t buf)
{
	if (!buf->offset) { // the start of buffer pop
		punit->sync_pkt[0].pkt_num = buf->user.nHighPart;
		punit->sync_pkt[0].pts = buf->user.nLowPart;
		memset((void *)&punit->sync_pkt[1], 0, sizeof(media_pkt_sync_t));
	}

	return 0;
}
#endif

static XA_ERRORCODE XaDec_ProcessInputJunk(xa_dec_unit_t *punit, psysbuf_t buf)
{
	XA_ERRORCODE err_code = XA_NO_ERROR;
	uint32_t ui_init_done = 0;
	int32_t len, i_bytes_consumed;
	phys_addr_t src, dst;

	if (punit == NULL || buf == NULL || punit->junkdone)
		return XA_NO_ERROR;

#ifdef MEDIA_AV_SYNC
	update_pts(punit, buf);
#endif

	len = buf->size;
	if (len > punit->input.maxsize - punit->input.size)
		len = punit->input.maxsize - punit->input.size;

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	if (len <= 0) {
		buf->size = 0;
		return XA_NO_ERROR;
	}
#else
	if (len <= 0)
		return XA_NO_ERROR;
#endif

	src = buf->haddr + buf->offset;
	dst = punit->input.haddr + punit->input.size;

#ifdef XA_USE_EDMA_IOBUF
	dma_cache_wback_inv(dst, len);
	edma_copy(EDMA_CHN_IO, dst, src, len);
#else
	memcpy((void*)dst, (void*)src, len);
#endif

	punit->input.size += len;
	buf->offset += len;
	buf->size -= len;

	/* Tell that the input is over in this buffer */
	if ((buf->flags & SYS_BUF_FLAG_EOF) && (buf->size < 1)) {
		punit->eof = 1;
		API_CALL(XA_API_CMD_INPUT_OVER, 0, NULL, "Input Over - Error");
	}

	/* Set number of bytes to be processed */
	i_bytes_consumed = punit->input.size;
	API_CALL(XA_API_CMD_SET_INPUT_BYTES, 0, &i_bytes_consumed, "Setting input-Bytes Error");

	/* Initialize the process */
	API_CALL(XA_API_CMD_INIT, XA_CMD_TYPE_INIT_PROCESS, NULL, "Init-processing-error");

	/* Checking for end of initialization */
	API_CALL(XA_API_CMD_INIT, XA_CMD_TYPE_INIT_DONE_QUERY, &ui_init_done, "End of Init detect Error");

	/* How much buffer is used in input buffers */
	i_bytes_consumed = 0;
	API_CALL(XA_API_CMD_GET_CURIDX_INPUT_BUF, 0, &i_bytes_consumed, "Input Buffer Consumed Check Error");

	/* Read and Adjust current stream position */
	if (i_bytes_consumed > 0) {
		punit->input.size = xa_shift_input_buffer(
			(signed char *)(punit->input.haddr),
			punit->input.size, i_bytes_consumed
		);
	}

	if (ui_init_done == 1) {
		punit->junkdone = 1;
		return 1;
	}

	return XA_NO_ERROR;

}

static psysbuf_t XaDec_ProcessInput(xa_dec_unit_t *punit, psysbuf_t buf)
{
	XA_ERRORCODE err_code = XA_NO_ERROR;
	uint32_t ui_exec_done = 0;
	int32_t len, i_bytes_consumed, i_out_bytes;
	psysbuf_t outbuf = NULL;

	if (punit == NULL || buf == NULL || !punit->junkdone)
		return NULL;

	/* read-write memory, for usable output, intra frame */
	if (punit->audiobuf == NULL) {
		punit->audiobuf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
	}
	if (punit->audiobuf == NULL)
		return NULL;

#ifdef MEDIA_AV_SYNC
	int ibuf_start_flag = 0;
	if (!buf->offset) // the start of pop's buffer
		ibuf_start_flag = 1;
#endif

	len = buf->size;
	if (len > punit->input.maxsize - punit->input.size)
		len = punit->input.maxsize - punit->input.size;

	if (len > 0)
	{
		phys_addr_t src, dst;

		src = buf->haddr + buf->offset;
		dst = punit->input.haddr + punit->input.size;

#ifdef XA_USE_EDMA_IOBUF
		dma_cache_wback_inv(dst, len);
		edma_copy(EDMA_CHN_IO, dst, src, len);
#else
		memcpy((void*)dst, (void*)src, len);
#endif

		punit->input.size += len;
		buf->offset += len;
		buf->size -= len;
	}
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	else
		buf->size = 0;
#endif

	/* Tell that the input is over in this buffer */
	if ((buf->flags & SYS_BUF_FLAG_EOF) && (buf->size < 1)) {
		punit->eof = 1;
		API_CALLR(XA_API_CMD_INPUT_OVER, 0, NULL, "Input Over - Error", NULL);
	}

	/* Set number of bytes to be processed */
	i_bytes_consumed = punit->input.size;
	API_CALLR(XA_API_CMD_SET_INPUT_BYTES, 0, &i_bytes_consumed, "Setting input-Bytes Error", NULL);

	/* Execute process */
	API_CALLR(XA_API_CMD_EXECUTE, XA_CMD_TYPE_DO_EXECUTE, NULL, "Decoding Error", NULL);

	/* Checking for end of processing */
	API_CALLR(XA_API_CMD_EXECUTE, XA_CMD_TYPE_DONE_QUERY, &ui_exec_done, "End-exec Check Error", NULL);

	/* Get the output bytes */
	API_CALLR(XA_API_CMD_GET_OUTPUT_BYTES, 0, &i_out_bytes, "Get Output Bytes Error", NULL);

	/* How much buffer is used in input buffers */
	i_bytes_consumed = 0;
	API_CALLR(XA_API_CMD_GET_CURIDX_INPUT_BUF, 0, &i_bytes_consumed, "Input Buffer Consumed Check Error", NULL);

	/* Read and Adjust current stream position */
	if (i_bytes_consumed > 0) {
		punit->input.size = xa_shift_input_buffer(
			(signed char *)(punit->input.haddr),
			punit->input.size, i_bytes_consumed
		);
#ifdef MEDIA_AV_SYNC
		if (ibuf_start_flag) {
			// if the session buffer is not a full packet, ignore the sync_pkt
			if (buf->flags) {
				if (punit->input.size > 0) {
					// only cache one packet most
					memcpy((void *)&punit->sync_pkt[0], (void *)&punit->sync_pkt[1], sizeof(media_pkt_sync_t));
					punit->sync_pkt[1].pkt_num = buf->user.nHighPart;
					punit->sync_pkt[1].pts = buf->user.nLowPart;
				} else {
					punit->sync_pkt[0].pkt_num = buf->user.nHighPart;
					punit->sync_pkt[0].pts = buf->user.nLowPart;
					memset((void *)&punit->sync_pkt[1], 0, sizeof(media_pkt_sync_t));
				}
			}
		}
#endif
	}

	/* Check output */
	if (i_out_bytes > 0)
	{
		phys_addr_t src, dst;

		outbuf = punit->audiobuf;

		src = punit->output.haddr;
		dst = outbuf->haddr + outbuf->size;
#ifdef MEDIA_AV_SYNC
		if (!outbuf->size) { // if the out buffer start
			outbuf->user.nHighPart = punit->sync_pkt[0].pkt_num;
			outbuf->user.nLowPart = punit->sync_pkt[0].pts;
			memcpy((void *)&punit->sync_pkt[0], (void *)&punit->sync_pkt[1], sizeof(media_pkt_sync_t));
		}
#endif

		if (punit->audiocfg.samplingsize == 24) {
			i_out_bytes = buffer_32_to_24_bits((UWORD8 *)dst, (UWORD32 *)src, (long)i_out_bytes);
		}
		else {
#ifdef XA_USE_EDMA_IOBUF
			dma_cache_wback_inv(src, i_out_bytes);
			edma_copy(EDMA_CHN_IO, dst, src, i_out_bytes);
#else
			memcpy((void*)dst, (void*)src, i_out_bytes);
#endif
		}

		outbuf->size += i_out_bytes;
		if (outbuf->size < punit->audio_valid_size)
			outbuf = NULL;
		else {
			punit->audiobuf = NULL;
			if (punit->eof)
				outbuf->flags |= SYS_BUF_FLAG_EOF;
			else
				outbuf->flags &= ~SYS_BUF_FLAG_EOF;
		}

	}

	return outbuf;

}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
// SESSION
///////////////////////////////////////////////////////////////////////////////////////////////

typedef struct
{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// xa_dec
	xa_dec_unit_t xa_dec;
#endif
	// session
	struct SESSION *session, *session_audio;

	long load_size, load_offset, load_szcount;
	psysbuf_t load_buf, in_buf;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	psysbuf_t audiobuf;
#endif
	FIL file;
#ifdef MEDIA_AV_SYNC
	session_av_sync *sync;
#endif
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
    AVCodecContext *c;
    AVCodecParserContext *parser;
    AVPacket *pkt;
    AVFrame *decoded_frame;
	int codec_type;
	AVCodec *codec;
#endif
}session_unit_t;

#ifndef MEDIA_SAVE_DEMUX_FILES
static long SESSIONAPI(StreamLoad)(session_unit_t *punit)
{
	uint8_t *ldat;
	long l2, off, ret = 0;
	psysbuf_t load_buf;

	if (punit->in_buf != NULL)
		return 0;

	load_buf = punit->load_buf;
	if (load_buf == NULL) {
		load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (load_buf == NULL)
			return -1;
		punit->load_buf = load_buf;
	}

	if (punit->load_size > 0)
	{
		l2 = load_buf->maxsize;
		if (l2 >= punit->load_size) {
			l2 = punit->load_size;
			load_buf->flags |= SYS_BUF_FLAG_EOF;
		}

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		f_read(&(punit->file), (void*)(load_buf->haddr), l2, NULL);
#else
		f_read(&(punit->file), (void*)HWADDR(load_buf->haddr), l2, NULL);
#endif
		load_buf->size = l2;
		punit->load_offset += l2;
		punit->load_size -= l2;

		l2 = punit->load_offset - punit->load_szcount;
		if ((l2 > punit->load_size / 100L) || (l2 > 0x40000)) {
			punit->load_szcount = punit->load_offset;
			debug("\t\tProcess : %f%%, 0x%lx (%ld) bytes\n",
				100.0f*(float)punit->load_offset/(float)(punit->load_offset+punit->load_size),
				punit->load_offset, punit->load_offset
				);
		}
	}
	else
	{
		psysbuf_t buf = SessionBufferPop(punit->session);
		if (buf) {
			if (buf->list.group == load_buf->list.group) {
				load_buf = buf;
			}
			else {
				phys_addr_t src, dst;
				src = buf->haddr + buf->offset;
				dst = load_buf->haddr + load_buf->offset;
#ifdef XA_USE_EDMA_IOBUF
				edma_copy(EDMA_CHN_IO, dst, src, buf->size);
#else
				memcpy((void*)dst, (void*)src, buf->size);
#endif
				load_buf->size = buf->size;
				if (buf->flags & SYS_BUF_FLAG_EOF)
					load_buf->flags |= SYS_BUF_FLAG_EOF;
				load_buf->user = buf->user;
				sysbuf_free(buf);
			}
		}
		else {
			load_buf = NULL;
		}
	}

	if (load_buf) {
		punit->in_buf = load_buf;
		if (punit->load_buf == load_buf)
			punit->load_buf = NULL;
		ret = load_buf->size;
	}

	return ret;
}
#endif
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                   session_unit_t *punit)
{
    int i, ch;
    int ret, data_size;
	psysbuf_t buf = NULL;

    /* send the packet with the compressed data to the decoder */
    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error submitting the packet to the decoder\n");
//        exit(1);
//		return;
    }
    /* read all the output frames (in general there may be any number of them */
    while (ret >= 0) {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
////            fprintf(stderr, "Error during decoding\n");
//            exit(1);
			return;
        }
//		debug("\t\ta frame %3d\n", dec_ctx->frame_number);

        data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
        if (data_size < 0) {
            /* This should not occur, checking just for paranoia */
            fprintf(stderr, "Failed to calculate data size\n");
            exit(1);
        }

		if (punit->audiobuf == NULL) {
			punit->audiobuf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		}
		if (punit->audiobuf == NULL)
			return;
		buf = punit->audiobuf;
		buf->user.nHighPart = 0;
		buf->user.nLowPart = frame->pkt_pts;

		while (SessionBufferTopNum(punit->session_audio) > 2) {
			if (punit->session_audio != NULL) {
				SESSIONSTATE audio_state = punit->session_audio->SessionRun(punit->session_audio);
				if (audio_state == SSTATE_STOP) {
					return;
				}
			}
			continue;
		}

        for (i = 0; i < frame->nb_samples; i++)
            for (ch = 0; ch < dec_ctx->channels; ch++) {
#ifdef CONFIG_MEDIA_SAVE_DECODE_FILES
				//pcm play cmd: ffplay -ac 2 -ar 44100 -f f32le ffadecoder.pcm
				fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
#endif
				memcpy((void *)buf->haddr + buf->size, frame->data[ch] + data_size*i, data_size);
				buf->size += data_size;
			}
		if (buf) {
			if (punit->session_audio != NULL) {
				session_buffer_t *sbf = SessionBufferPush(punit->session_audio, buf);
				if (sbf == NULL)
					sysbuf_free(buf);
			}
			else {
				sysbuf_free(buf);
			}
		}
		punit->audiobuf = NULL;
    }
}
#endif
#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
static int session_noirq_running(session_unit_t *punit)
{
	uint32_t flags;
	psysbuf_t load_buf;

	struct SESSION *session;
	if (punit == NULL)
		return -EACCES;

	session = punit->session;
	if (session == NULL)
		return -EFAULT;

	if(session->state <= SSTATE_INITED
		|| session->state == SSTATE_RUNNING_PAUSE )
		return 0;

	local_irq_save(flags);

	load_buf = SessionBufferPop(session);
	if (load_buf) {
		if (punit->file != NULL) {
			f_write(&(punit->file), (void*)(load_buf->haddr + load_buf->offset), load_buf->size, NULL);
		}
		sysbuf_free(load_buf);
	}

	session->state = SSTATE_RUNNING_IDLE;

	local_irq_restore(flags);

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	session_noirq_running(punit);

	return 0;
}
#endif
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
static uint32_t lastLowPart;
static uint32_t lastHighPart;
static uint32_t nowLowPart = 0;
static uint32_t nowHighPart = 0;
#endif
static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	SESSIONSTATE audio_state;

	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

	if (session->state != SSTATE_RUNNING
		&& session->state != SSTATE_RUNNING_IDLE)
		return session->state;
#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
	session_noirq_running(punit);
#endif
#ifndef CONFIG_MEDIA_SAVE_DEMUX_FILES
	/* try to process stream buffer */
	SESSIONAPI(StreamLoad)(punit);

	/* nothing done then, get and free */
	if (punit->in_buf != NULL)
	{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		psysbuf_t buf = NULL;

		session->state = SSTATE_RUNNING;

		/* process buffer */
		if (punit->xa_dec.junkdone) {
			buf = XaDec_ProcessInput(&punit->xa_dec, punit->in_buf);
		}
		else {
			WORD32 junkdone = XaDec_ProcessInputJunk(&punit->xa_dec, punit->in_buf);
			if (junkdone == 1) {
				XaDec_GetParams(&punit->xa_dec);
				SessionCommand(punit->session_audio, SSCMD_SET_AUDIO_INFO, &punit->xa_dec.audiocfg);
				SessionCommand(punit->session_audio, SSCMD_STREAM_GETVALIDINSIZE, &punit->xa_dec.audio_valid_size);
			}
		}
#endif
		if (punit->audiobuf == NULL) {
			punit->audiobuf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		}
		if (punit->audiobuf == NULL)
			return session->state;
		if (punit->in_buf) {
			if (punit->in_buf->size < 1) {
				sysbuf_free(punit->in_buf);
				punit->in_buf = NULL;
				return session->state;
			}
		}
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		if (punit->in_buf->size < 1) {
			sysbuf_free(punit->in_buf);
			punit->in_buf = NULL;
		}

		if (buf) {
			if (punit->session_audio != NULL) {
				session_buffer_t *sbf = SessionBufferPush(punit->session_audio, buf);
				if (sbf == NULL)
					sysbuf_free(buf);
			}
			else {
				sysbuf_free(buf);
			}
		}
#endif

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		lastLowPart = nowLowPart;
		lastHighPart = nowHighPart;
		nowLowPart = punit->in_buf->user.nLowPart;
		nowHighPart = punit->in_buf->user.nHighPart;
		int ret = av_parser_parse2(punit->parser, punit->c, &punit->pkt->data, &punit->pkt->size,
                               (const uint8_t *)punit->in_buf->haddr + punit->in_buf->offset, punit->in_buf->size,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
		punit->in_buf->offset += ret;
		punit->in_buf->size -= ret;
        if (ret < 0) {
            fprintf(stderr, "Error while parsing\n");
            exit(1);
        }

        if (punit->pkt->size) {
			punit->pkt->pts = lastLowPart;
			punit->pkt->dts = lastHighPart; // save packet number in pkt_dts item
            decode(punit->c, punit->pkt, punit->decoded_frame, punit);
		}
#endif
	}

	/* run audio part */
	if (punit->session_audio != NULL) {
		audio_state = punit->session_audio->SessionRun(punit->session_audio);
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		if (audio_state == SSTATE_STOP) {
#else
		if (punit->xa_dec.eof && audio_state == SSTATE_STOP) {
#endif
			session->state = SSTATE_STOP;
			return session->state;
		}
	}

	if (punit->in_buf != NULL || SessionBufferTopNum(punit->session) > 1
			|| SessionBufferTopNum(punit->session_audio) > 1) // modify on 20210111, fix the bug that video speed limited by i2sout speed
		session->state = SSTATE_RUNNING;
	else
		session->state = SSTATE_RUNNING_IDLE;
#endif
	return session->state;
}

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
	const char *filename;
#endif
	int res;
	session_unit_t *punit = NULL;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	XA_ERRORCODE err_code = XA_NO_ERROR;
#endif
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_INITED)
		return -EPERM;

	/* Initial loading */
	punit->in_buf = NULL;
	punit->load_buf = NULL;
	punit->load_size = 0;
	punit->load_offset = 0;
	punit->load_szcount = 0;
	punit->session_audio = NULL;

	////////////////////////////////////////////////////////////////////////
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// xa_dec
	memset(&punit->xa_dec, 0, sizeof(punit->xa_dec));

	punit->xa_dec.streamcfg.samplingrate = XaDec_GetNearlySamplingrate(file->ta.sample_rate);
	punit->xa_dec.streamcfg.samplingsize = file->ta.sample_size;
	punit->xa_dec.audio_valid_size = 64 * 1024;
	punit->xa_dec.outnchans = file->ta.channel;

	// load library
	err_code = XaDec_LoadLibrary(&punit->xa_dec);
	if (err_code & XA_FATAL_ERROR)
		return -EACCES;

	// prepare process
	err_code = XaDec_Prepare(&punit->xa_dec);
	if (err_code & XA_FATAL_ERROR) {
		XaDec_FreeLibrary(&punit->xa_dec);
		return -EFAULT;
	}
#endif
	////////////////////////////////////////////////////////////////////////
	punit->session_audio = SessionGet(SST_I2SOUT);

	if (punit->session_audio != NULL) {
		session_file_t fl;
		fl.type = file->type;
		fl.ftype = file->ftype;
		fl.tv = file->tv;
		fl.ta = file->ta;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		fl.ta.sample_size = 32; // the ffavcodec output is always 32bit float
#endif
		fl.filename = NULL;
#ifdef MEDIA_AV_SYNC
		fl.sync = file->sync;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		if (file->filename)
			fl.sync->a_real_rate = file->ta.sample_rate;
#endif
#endif
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_START, &fl);
		if (res) {
			debug("start audio session error %d\n", res);
			punit->session_audio = NULL;
		}

	}

	if (file->filename) {
		if (f_open(&(punit->file), file->filename, FA_READ) != FR_OK)
		{
			debug("open stream file error \n");
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
			XaDec_Release(&punit->xa_dec);
			XaDec_FreeLibrary(&punit->xa_dec);
			punit->session_audio = NULL;
#endif
			return -EBADF;
		}
		else
		{
			punit->load_size = f_size(&(punit->file));
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
			debug("audio stream file len: %d\n", punit->load_size);
#endif
		}
	}

#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
	// initial
	switch(file->type)
	{
	case SST_MP3:
		{
			filename = "mp3.mp3";
		}break;
	default:
		filename = "mp3_default.mp3";
	};

	if(f_open(&(punit->file), filename, FA_WRITE|FA_CREATE_ALWAYS)!= FR_OK)
	{
		debug("create mp3 output file error \n");
	}
#endif

	SessionBufferDeInit(session);

	session->state = SSTATE_RUNNING_IDLE;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	switch (file->type) {
	case SST_H264:
		punit->codec_type = AV_CODEC_ID_H264;
		break;
	case SST_HEVC:
		punit->codec_type = AV_CODEC_ID_HEVC;
		break;
	case SST_MPG2:
		punit->codec_type = AV_CODEC_ID_MPEG2VIDEO;
		break;
	case SST_MPG4:
		punit->codec_type = AV_CODEC_ID_MPEG4;
		break;
	case SST_AAC:
	case SST_AAC_ADIF:
	case SST_AAC_ADTS:
	case SST_AAC_LOAS:
	case SST_AAC_LATM:
		punit->codec_type = AV_CODEC_ID_AAC;
		break;
	case SST_MP3:
		punit->codec_type = AV_CODEC_ID_MP3;
		break;
	default:
		debug("Steam type: %d not suppurt!\n", file->type);
		break;
	}

    punit->pkt = av_packet_alloc();
    /* find the audio decoder */
    punit->codec = avcodec_find_decoder(punit->codec_type);
    if (!punit->codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
    punit->parser = av_parser_init(punit->codec->id);
    if (!punit->parser) {
        fprintf(stderr, "Parser not found\n");
        exit(1);
    }
    punit->c = avcodec_alloc_context3(punit->codec);
    if (!punit->c) {
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    }

    /* open it */
    if (avcodec_open2(punit->c, punit->codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
	if (!punit->decoded_frame) {
		if (!(punit->decoded_frame = av_frame_alloc())) {
			fprintf(stderr, "Could not allocate audio frame\n");
			exit(1);
		}
	}
#endif
#ifdef CONFIG_MEDIA_SAVE_DECODE_FILES
	outfile = fopen("ffadecoder.pcm", "wb");
#endif

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	int res;
	SESSIONSTATE state;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
#ifdef MEDIA_SAVE_DEMUX_FILES
	if (punit == NULL)
#else
	if (punit == NULL || session->state < SSTATE_RUNNING)
#endif
		return -EPERM;

	// set state
	session->state = SSTATE_STOP;

	////////////////////////////////////////////////////////////////////////
	// Audio

	if (punit->session_audio != NULL) {
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
		if (res) {
		}
		punit->session_audio = NULL;
	}
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	////////////////////////////////////////////////////////////////////////
	// xa_dec

	// release process
	XaDec_Release(&punit->xa_dec);

	// xa_dec
	XaDec_FreeLibrary(&punit->xa_dec);
#endif
	////////////////////////////////////////////////////////////////////////
	if (punit->in_buf) {
		sysbuf_free(punit->in_buf);
		punit->in_buf = NULL;
	}

	if (punit->load_buf) {
		sysbuf_free(punit->load_buf);
		punit->load_buf = NULL;
	}

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	if (punit->file)
		f_close(&punit->file);
#else
	f_close(&punit->file);
#endif

	SessionBufferDeInit(session);

	// set state
	session->state = SSTATE_INITED;

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
    avcodec_free_context(&punit->c);
    av_parser_close(punit->parser);
    av_frame_free(&punit->decoded_frame);
    av_packet_free(&punit->pkt);
#endif

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(struct SESSION *session, uint32_t *psz)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	if (psz) {
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		*psz = 0;
#else
		*psz = 3 * punit->xa_dec.proc_mem_tabs[XA_MEMTYPE_INPUT].size;
#endif
	}

	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
	case SSCMD_STREAM_START:
	{
		debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
	}break;
	case SSCMD_STREAM_PAUSE:
	{
		debug("Session '%s' command  'SSCMD_STREAM_PAUSE'\n", session->name);
		if (session->state == SSTATE_RUNNING
			|| session->state == SSTATE_RUNNING_IDLE)
			session->state = SSTATE_RUNNING_PAUSE;
	}break;
	case SSCMD_STREAM_RESUME:
	{
		debug("Session '%s' command  'SSCMD_STREAM_RESUME'\n", session->name);
		if (session->state == SSTATE_RUNNING_PAUSE)
			session->state = SSTATE_RUNNING;
	}break;
	case SSCMD_STREAM_STOP:
	{
		debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_STOP)(session);
	}break;
	case SSCMD_STREAM_BUFCHANGED:
	{
#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
		return SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(session);
#endif
	}break;
	case SSCMD_STREAM_SKIP:
	{
	}break;
	case SSCMD_STREAM_GETVALIDINSIZE:
	{
		return SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(session, (uint32_t *)params);
	}break;
	default:
	{
		debug("Session '%s' command %d unknown !\n", session->name, cmd);
		ret = -EINVAL;
	};
	};

	return ret;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	else
		return;

	debug("SessionDeInit : %s ..\n", session->name);

	SessionBufferDeInit(session);

	if (punit != NULL) {
		// session
		free(punit);
	}

	session->handle = NULL;
}

static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	XA_ERRORCODE err_code = XA_NO_ERROR;
#endif
	session_unit_t *punit = (session_unit_t *)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));
	punit->session = session;

	////////////////////////////////////////////////////////////////////////
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;

	SessionBufferInit(session);

	session->handle = (SESSIONHANDLE)punit;

	return session->handle;
}


///////////////////////////////////////////////////////////////////////////////

struct SESSION gSession_xamp3 =
{ "xamp3", (1 << SST_MP3)
	, &SESSIONAPI(SessionInit),
};
